// GlobalResources.cpp
//
#include "GlobalResources.h"

#include <QTextStream>
#include <QGLWidget>
#include <windows.h>

#include "MemCheck.h"


using namespace IDS::IDEAS;

GmVectorFile				GlobalResources::geoBorders;
std::string					GlobalResources::resourceDir;
unsigned char				GlobalResources::preselectColor[4];
unsigned char				GlobalResources::selectColor[4];
int							GlobalResources::initialized	  = 0;
bool						GlobalResources::attachedConsole = false;

IDS::OpenGL::Program		GlobalResources::vpointShader;
IDS::OpenGL::Program		GlobalResources::geoImageShader;
IDS::OpenGL::Program		GlobalResources::rasterDataShader;

IDS::OpenGL::SymbolsMap		GlobalResources::symbolsMap;
IDS::OpenGL::FontTexture	GlobalResources::arial;
IDS::OpenGL::TerrainShading	GlobalResources::terrainShader;
QGLWidget					*GlobalResources::context = NULL;

// ------------------------------------------------------------------------------------------------
void GlobalResources::destroy()
{
	initialized--;

	if( initialized > 0 )
		return;

	symbolsMap.destroy();
	arial.destroy();
	terrainShader.destroy();

	rasterDataShader.clear();
	vpointShader.clear();
	geoImageShader.clear();
	
	if(context)
	{
		delete context;
		context = NULL;
	}
	
	if(OpenGL::startingContext)
	{
		if (OpenGL::startingContext == glewGetContext())
			glewSetContext(NULL);

		delete OpenGL::startingContext;
		OpenGL::startingContext = NULL;
	}
}


// ------------------------------------------------------------------------------------------------
bool GlobalResources::init(const std::string &resourcePath, std::string &errorLog, bool console)
{
	initialized++;
	errorLog = "";

	if(console)
	{
		AllocConsole();
		freopen ("CONOUT$", "w", stdout );
		attachedConsole = true;
	}


	if(initialized > 1)
	{
		errorLog = "Resources already loaded.\n";
		return true;
	}


	resourceDir     = resourcePath;
	attachedConsole = false;

	if ((QGLFormat::openGLVersionFlags() & QGLFormat::OpenGL_Version_2_0) == 0)
	{
		errorLog = "OpenGL version 2.0 or higher is required.\n";
		return false;
	}

	QGLFormat format;
	format.setStencil(false);
	format.setSampleBuffers(false);

	context			= new QGLWidget(format);
	context->makeCurrent();
	
	OpenGL::startingContext = new GLEWContext;
	glewSetContext(OpenGL::startingContext);

#ifdef _USE_GLEW_
	glewExperimental = GL_TRUE;
	if(glewInit() != GLEW_OK)
	{
		errorLog = "GLEW Initialization failed.\n";
		return false;
	}
#endif

	QString ext   ( (char *)glGetString(GL_EXTENSIONS) );
	QString vendor( (char *)glGetString(GL_VENDOR) );
	QString ver   ( (char *)glGetString(GL_VERSION) );
	QString rend  ( (char *)glGetString(GL_RENDERER) );

	QTextStream s(&ext);

	OpenGL::glExtensions.clear();
	while( !s.atEnd() )
	{
		QString str; s >> str;
		
		if( str.length()>0 )
			OpenGL::glExtensions.push_back(str.toStdString());
	}

	printf( "Graphic adapter:      %s\n",vendor.toLocal8Bit().data() );
	printf( "OpenGL renderer:      %s\n",rend.toLocal8Bit().data() );
	printf( "OpenGL Version:       %s\n",ver.toLocal8Bit().data() );
	if( OpenGL::supportMemoryQuery() )
		printf( "Total Graphic Memory: %d MB\n", OpenGL::glGetTotalMemory() >> 10);
	else
		printf( "Total Graphic Memory: Unknown\n");


	std::string file = resourcePath+std::string("/GLVersion.txt");
	FILE *f = fopen(file.c_str(),"wt");
	if(f)
	{
		fprintf( f,"Graphic adapter:      %s\n",vendor.toLocal8Bit().data() );
		fprintf( f,"OpenGL renderer:      %s\n",rend.toLocal8Bit().data() );
		fprintf( f,"OpenGL Version:       %s\n",ver.toLocal8Bit().data() );
		if( OpenGL::supportMemoryQuery() )
			fprintf( f,"Total Graphic Memory: %d MB\n", OpenGL::glGetTotalMemory() >> 10);
		else
			fprintf( f,"Total Graphic Memory: Unknown\n");

		fprintf( f,"\n--------------------- Supported Extensions ---------------------\n\n");
		for(size_t i=0;i<OpenGL::glExtensions.size(); ++i)
			fprintf( f,"%s\n",OpenGL::glExtensions[i].c_str() );
		fclose(f);
	}

	if( !OpenGL::FBO::isSupported() )		{ errorLog = "OpenGL Error: FBO not supported.\n";		return false;}
	if( !OpenGL::Program::isSupported() )	{ errorLog = "OpenGL Error: Shaders not supported.\n";	return false;}


	// default colors (may be overwritten by custom settings)
	IDS::IDEAS::ViewInterface::Style reds;
	reds.color.setRed();
	reds.lineWidth = 1.5;
	reds.pointSize = 24;
	reds.penStyle  = IDS::IDEAS::ViewInterface::Style::Filled;

	IDS::IDEAS::OpenGLView::VObjectGL::setRedLineStyle( reds );
	IDS::IDEAS::OpenGLView::VObjectGL::setPreselectionColor(0xFF,0xFF,0xCD,0x64);
	IDS::IDEAS::OpenGLView::VObjectGL::setSelectionColor(0x00,0xFF,0xFF,0x64);
	IDS::IDEAS::OpenGLView::VObjectGL::setRedlineColor(0xFF,0x00,0x00,0xFF);
	IDS::IDEAS::OpenGLView::VObjectGL::setEditableObjectColor(0xFF,0xFF,0x00,0xFF);
	
	
	//terrainShader.addColor(-100.f,  OpenGL::Color(255,  0,255,255) );
	//terrainShader.addColor(-10.f,	  OpenGL::Color(  0,  0, 64,255) );
	//terrainShader.addColor( 0.f,	  OpenGL::Color( 50, 52,158,255) );
	//terrainShader.addColor( 10.f,   OpenGL::Color(146,175,234,255) );
	//terrainShader.addColor( 20.f,   OpenGL::Color( 1,98,70,255) );
	//terrainShader.addColor( 50.f,   OpenGL::Color( 23,125, 49,255) );
	//terrainShader.addColor( 250.f,  OpenGL::Color(231,212,123,255) );
	//terrainShader.addColor( 1000.f, OpenGL::Color(164, 73,  5,255) );
	//terrainShader.addColor( 2000.f, OpenGL::Color(129, 32, 32,255) );
	//terrainShader.addColor( 4000.f, OpenGL::Color(180,180,180,255) );
	//terrainShader.addColor( 6000.f, OpenGL::Color(248,248,248,255) );
	//terrainShader.addColor( 9000.f, OpenGL::Color(255,255,255,255) );
	terrainShader.destroyColorRamp();
	terrainShader.addColor(-100.f,  OpenGL::Color(255,  0,255,255) );
	terrainShader.addColor(-10.f,	OpenGL::Color(  0,  0, 64,255) );
	terrainShader.addColor( 0.f,	OpenGL::Color( 50, 52,158,255) );
	terrainShader.addColor( 10.f,   OpenGL::Color(146,175,234,255) );
	terrainShader.addColor( 20.f,   OpenGL::Color( 1,98,70,255) );
	terrainShader.addColor( 50.f,   OpenGL::Color( 23,125, 49,255) );
	terrainShader.addColor( 250.f,  OpenGL::Color(231,212,123,255) );
	terrainShader.addColor( 1000.f, OpenGL::Color(164, 73,  5,255) );
	terrainShader.addColor( 2000.f, OpenGL::Color(128, 64, 64,255) );
	terrainShader.addColor( 4000.f, OpenGL::Color(83,44,0,255) );
	terrainShader.addColor( 6000.f, OpenGL::Color(244,255,255,255) );
	terrainShader.addColor( 9000.f, OpenGL::Color(255,255,255,255) );
	
		
	//terrainColorMap.addColor( 0.0,	  OpenGL::ColorRamp::Color(  0,  0, 64,255) );
	//terrainColorMap.addColor( 10.0,   OpenGL::ColorRamp::Color(136,177,255,255) );
	//terrainColorMap.addColor( 20.0,   OpenGL::ColorRamp::Color( 89,158,115,255) );
	//terrainColorMap.addColor( 30.0,   OpenGL::ColorRamp::Color(  0,115, 89,255) );
	//terrainColorMap.addColor( 100.0,  OpenGL::ColorRamp::Color( 64,127, 64,255) );
	//terrainColorMap.addColor( 200.0,  OpenGL::ColorRamp::Color(186,183, 69,255) );
	//terrainColorMap.addColor( 500.0,  OpenGL::ColorRamp::Color(211,208,130,255) );
	//terrainColorMap.addColor( 1000.0, OpenGL::ColorRamp::Color(105, 86, 41,255) );
	//terrainColorMap.addColor( 2000.0, OpenGL::ColorRamp::Color(221,253,255,255) );
	printf("\n*** Compiling Shaders ***\n");
	terrainShader.init(1024);
	initVPointShader();
	initGeoImageShader();
	initRasterDataShader();
	printf("\n");

	bool gb  = geoBorders.isLoaded() ? true : geoBorders.load(resourcePath + "/world.xyz" );
	bool sym = symbolsMap.isLoaded() ? true : symbolsMap.load(resourcePath + "/Symbols/IDEASsymbols.png",  8, 8);
	bool fnt = arial.isLoaded()      ? true : arial.load(resourcePath + "/Fonts/arial_small.fnt");
	arial.setImmediateMode(false);

	if( !sym )	errorLog += "Unable to load graphic symbols.\n";
	if( !fnt )	errorLog += "Unable to load graphic fonts.\n";
	if( !gb )	errorLog += "Unable to load geoborders.\n";

	return fnt && sym /*&& gb*/;
}

// ------------------------------------------------------------------------------------------------
void GlobalResources::initGeoImageShader()
{
	if(geoImageShader.isValid())
		geoImageShader.clear();

	const std::string vs = STRINGIFY
	(
		void main()
		{
			gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;
			gl_Position       = gl_ModelViewProjectionMatrix * gl_Vertex;
			gl_FrontColor     = gl_Color;
		}
	);

	const std::string fs = STRINGIFY
	(
		uniform sampler2D u_clipTexture;
		uniform sampler2D u_imgTexture;
		uniform vec2 u_sizes;
		void main()
		{
			vec4 imgTexel  = texture2D(u_imgTexture, gl_TexCoord[0].xy ); 
			vec2 uvClip    = gl_FragCoord.xy * u_sizes; 
			vec4 clipTexel = texture2D(u_clipTexture, uvClip ); 
			
			if( clipTexel.r < 1.0 )
				discard; 
			
			gl_FragColor = imgTexel * gl_Color;
		}
	);

	geoImageShader.set(vs,fs);
	std::string log = geoImageShader.getInfoLog();
	
	printf("GeoImager Shader: %s",log.length() == 0 ? "Succeded\n" : log.c_str() );
}

// ------------------------------------------------------------------------------------------------
void GlobalResources::initVPointShader()
{
	if(vpointShader.isValid())
		vpointShader.clear();

	// -----------------------------------
	// -----------------------------------
	const std::string vs = STRINGIFY
	(
		uniform float u_pointSize;
		void main()
		{
			int vtxIndex = int(floor(gl_Color.a * 255.0));

			vec2 pos = gl_Vertex.xy;
			if( vtxIndex == 0 ) pos += vec2(-1.0, 1.0) * u_pointSize;
			else if( vtxIndex == 1 ) pos += vec2(-1.0,-1.0) * u_pointSize;
			else if( vtxIndex == 2 ) pos += vec2( 1.0,-1.0) * u_pointSize;
			else if( vtxIndex == 3 ) pos += vec2( 1.0, 1.0) * u_pointSize;

			//pos *= u_pointSize * 0.5;

			gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;
			gl_Position       = gl_ModelViewProjectionMatrix * vec4(pos.xy,gl_Vertex.zw);
			gl_FrontColor     = gl_Color;
		}
	);

	// -----------------------------------
	// -----------------------------------
	const std::string fs = STRINGIFY
	(
		uniform sampler2D u_symbolsMap;
		void main()
		{
			vec4 texel = texture2D(u_symbolsMap, gl_TexCoord[0].xy );
			gl_FragColor = texel * vec4(gl_Color.rgb,1.0);
		}	
	);

	vpointShader.set(vs,fs);
	std::string log = vpointShader.getInfoLog();
	
	printf("VPoint Shader: %s",log.length() == 0 ? "Succeded\n" : log.c_str() );
}

// ------------------------------------------------------------------------------------------------
void GlobalResources::initRasterDataShader()
{
	if(rasterDataShader.isValid())
		rasterDataShader.clear();
	
	// ----------------------------------
	//              VERTEX               
	// ----------------------------------
	const std::string vs = STRINGIFY
	(
		void main()
		{
			vec4 p = vec4( gl_Vertex.xy, 0.0, 1.0 );
			
			gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;
			gl_Position       = gl_ModelViewProjectionMatrix*p;
		}
	);

	// ----------------------------------
	//             FRAGMENT              
	// ----------------------------------
	const std::string fs = STRINGIFY
	(
		uniform int       u_gradient;
		uniform sampler2D u_tex;
		uniform vec4	  u_colors[64];
		uniform vec2	  u_rampValues[64];
		uniform int		  u_numValues;
		uniform vec2      u_heightMinMax;
		uniform vec2      u_rampMinMax;
			
		void main()
		{
			float texel = texture2D(u_tex, (gl_TexCoord[0].xy) ).r;

			if(texel <= 0.0) discard;
				
			float h    =	texel - (1.0/65535.0);           		// h in 0..1
			h         *= ( u_heightMinMax.y - u_heightMinMax.x );	// h in meters [0..(max-min)]
			h         += u_heightMinMax.x;							// h in meters [min..max]
			
			vec4 col;
			if ( u_gradient == 0 )
			{
				int colorIndex = 0;
				int i;
				for( i=0; i<u_numValues;++i )
				{
					if( h>u_rampValues[i].x && h<=u_rampValues[i].y )
					{
						colorIndex = i;
						break;
					}
				}
				col = i<u_numValues ? u_colors[colorIndex] : u_colors[u_numValues-1];
			}
			else
			{
				if( h<u_rampValues[0].y || h>u_rampValues[u_numValues-1].x)
					col = vec4(0.0);
				else
				{
					float minV = u_rampValues[1].x;
					float maxV = u_rampValues[2].x;

					vec4 col0 = u_colors[1];
					vec4 col1 = u_colors[2];

					float blendStep = (h - minV) / (maxV-minV); //h in 0..1

					col = mix(col0,col1,blendStep);
					//col = vec4(1.0);				
				}
			}
		
			gl_FragColor = col;
		}
	);

	rasterDataShader.set(vs,fs);
	std::string log =  rasterDataShader.getInfoLog();
	printf("RasterDataField Shader: %s",log.length() == 0 ? "Succeded\n" : log.c_str() );
}